/**
 * Copyright 2018-2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.util;

import java.lang.reflect.Constructor;

/**
 * 日志
 * 
 * @author jianggujin
 *
 */
public final class JLogFactory {

    private static Constructor<? extends JLog> logConstructor;

    static {
        tryImplementation(new Runnable() {
            public void run() {
                setImplementation(JLoggingAdapter.class);
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                setImplementation(JStdOutImpl.class);
            }
        });
    }

    private JLogFactory() {
    }

    public static JLog getLog(Class<?> clazz) {
        return getLog(clazz.getName());
    }

    public static JLog getLog(String logger) {
        try {
            return logConstructor.newInstance(logger);
        } catch (Throwable t) {
            throw new JLogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
        }
    }

    private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
            try {
                runnable.run();
            } catch (Throwable t) {
                // ignore
            }
        }
    }

    public static void setImplementation(Class<? extends JLog> implClass) {
        try {
            Constructor<? extends JLog> candidate = implClass.getConstructor(String.class);
            JLog log = candidate.newInstance(JLogFactory.class.getName());
            if (log.isDebugEnabled()) {
                log.debug("Logging initialized using '" + implClass + "' adapter.");
            }
            logConstructor = candidate;
        } catch (Throwable t) {
            throw new JLogException("Error setting Log implementation.  Cause: " + t, t);
        }
    }

    /**
     * 日志接口
     * 
     * @author jianggujin
     *
     */
    public static interface JLog {
        /**
         * 错误开启
         * 
         * @return 错误级别则返回true
         */
        public boolean isErrorEnabled();

        /**
         * 信息开启
         * 
         * @return 信息级别则返回true
         */
        public boolean isInfoEnabled();

        /**
         * 警告开启
         * 
         * @return 警告级别则返回true
         */
        public boolean isWarnEnabled();

        /**
         * 调试开启
         * 
         * @return 调试级别则返回true
         */
        public boolean isDebugEnabled();

        /**
         * 跟踪开启
         * 
         * @return 跟踪级别则返回true
         */
        public boolean isTraceEnabled();

        /**
         * 错误
         * 
         * @param msg  日志信息
         * @param args 日志参数
         */
        public void error(String msg, Object... args);

        /**
         * 错误
         * 
         * @param msg       日志信息
         * @param throwable 异常对象
         */
        public void error(String msg, Throwable throwable);

        /**
         * 调试
         * 
         * @param msg  日志信息
         * @param args 日志参数
         */
        public void debug(String msg, Object... args);

        /**
         * 调试
         * 
         * @param msg       日志信息
         * @param throwable 异常对象
         */
        public void debug(String msg, Throwable throwable);

        /**
         * 跟踪
         * 
         * @param msg  日志信息
         * @param args 日志参数
         */
        public void trace(String msg, Object... args);

        /**
         * 跟踪
         * 
         * @param msg       日志信息
         * @param throwable 异常对象
         */
        public void trace(String msg, Throwable throwable);

        /**
         * 警告
         * 
         * @param msg  日志信息
         * @param args 日志参数
         */
        public void warn(String msg, Object... args);

        /**
         * 警告
         * 
         * @param msg       日志信息
         * @param throwable 异常对象
         */
        public void warn(String msg, Throwable throwable);

        /**
         * 信息
         * 
         * @param msg  日志信息
         * @param args 日志参数
         */
        public void info(String msg, Object... args);

        /**
         * 信息
         * 
         * @param msg       日志信息
         * @param throwable 异常对象
         */
        public void info(String msg, Throwable throwable);

    }

    /**
     * 默认的控制台输出实现
     * 
     * @author jianggujin
     *
     */
    public static class JStdOutImpl implements JLog {
        // trace, debug, info, warn, error;
        private int level = 3;

        public JStdOutImpl(String clazz) {
            String level = System.getProperty("com.jianggujin.modulelink.util.JLogFactory.JStdOutImpl.level");
            if ("trace".equalsIgnoreCase(level)) {
                this.level = 1;
            } else if ("debug".equalsIgnoreCase(level)) {
                this.level = 2;
            } else if ("info".equalsIgnoreCase(level)) {
                this.level = 3;
            } else if ("warn".equalsIgnoreCase(level)) {
                this.level = 4;
            } else if ("error".equalsIgnoreCase(level)) {
                this.level = 5;
            }
        }

        @Override
        public boolean isTraceEnabled() {
            return this.level <= 1;
        }

        @Override
        public boolean isDebugEnabled() {
            return this.level <= 2;
        }

        @Override
        public boolean isInfoEnabled() {
            return this.level <= 3;
        }

        @Override
        public boolean isWarnEnabled() {
            return this.level <= 4;
        }

        @Override
        public boolean isErrorEnabled() {
            return this.level <= 5;
        }

        @Override
        public void error(String msg, Throwable throwable) {
            if (isErrorEnabled()) {
                System.err.println(msg);
                throwable.printStackTrace(System.err);
            }
        }

        @Override
        public void error(String msg, Object... args) {
            if (isErrorEnabled()) {
                System.err.println(JStringUtils.format(msg, args));
            }
        }

        @Override
        public void debug(String msg, Object... args) {
            if (isDebugEnabled()) {
                System.out.println(JStringUtils.format(msg, args));
            }
        }

        @Override
        public void trace(String msg, Object... args) {
            if (isTraceEnabled()) {
                System.out.println(JStringUtils.format(msg, args));
            }
        }

        @Override
        public void warn(String msg, Object... args) {
            if (isWarnEnabled()) {
                System.out.println(JStringUtils.format(msg, args));
            }
        }

        @Override
        public void info(String msg, Object... args) {
            if (isInfoEnabled()) {
                System.out.println(JStringUtils.format(msg, args));
            }
        }

        @Override
        public void debug(String msg, Throwable throwable) {
            if (isDebugEnabled()) {
                System.out.println(msg);
                throwable.printStackTrace(System.out);
            }
        }

        @Override
        public void trace(String msg, Throwable throwable) {
            if (isTraceEnabled()) {
                System.out.println(msg);
                throwable.printStackTrace(System.out);
            }
        }

        @Override
        public void warn(String msg, Throwable throwable) {
            if (isWarnEnabled()) {
                System.out.println(msg);
                throwable.printStackTrace(System.out);
            }
        }

        @Override
        public void info(String msg, Throwable throwable) {
            if (isInfoEnabled()) {
                System.out.println(msg);
                throwable.printStackTrace(System.out);
            }
        }
    }

    /**
     * 日志异常
     * 
     * @author jianggujin
     *
     */
    public static class JLogException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public JLogException() {
            super();
        }

        public JLogException(String message) {
            super(message);
        }

        public JLogException(String message, Throwable cause) {
            super(message, cause);
        }

        public JLogException(Throwable cause) {
            super(cause);
        }
    }
}
